#include <unistd.h>
#include <errno.h>
#include <stdexcept>
#include <Tools/String.h>

#include "Internal.h"

FileSystemImpl * Impl = NULL;
Thread * Syncer = NULL;

struct sBlockWriter
{
	int iFD;
	SizeType iBlockOffset;
	SizeType iFileOffset;

	sBlockWriter(int fd, SizeType blockOffset, SizeType fileOffset) : 
		iFD(fd), iBlockOffset(blockOffset), iFileOffset(fileOffset)
	{
	};

	SSizeType operator() (const unsigned char * data, SizeType dataLen) const
	{
		return ::pwrite(iFD, data + iBlockOffset, dataLen - iBlockOffset, iFileOffset);
	};
};

void FileSystem_SyncFile(BFile * file)
{
	assert(NULL != file);

	MBlockMap::iterator It = file->myBlockMap.begin();
	MBlockMap::iterator ItEnd = file->myBlockMap.end();

	for( ; It != ItEnd; ++It)
	{
		MBlock & block = It->second;
		if(block.isModified())
		{
			ReadLock _(*(block.myLock));
			SizeType iFileOffset = It->first * MBLOCK_DEFAULT_SIZE;
			SSizeType iNeedToWrite = block.getRealSize();
			SizeType iAlreadyWrited = 0;
			do
			{
				sBlockWriter myBlockWriter(file->iFD, iAlreadyWrited, iFileOffset);
				const SSizeType iRet = block.ReadData(myBlockWriter, SSizeType());

				if(-1 == iRet)
				{ /* try to recover!  */
					if((EINTR == errno)||(EAGAIN == errno))
						continue;
					fprintf(stderr, "Cannot write block into %d at %"PRId64": %d\n",
					    file->iFD, iFileOffset, errno);
					fflush(stderr);
					break;
				}
				iNeedToWrite -= iRet;
				iAlreadyWrited += iRet;
				iFileOffset += iRet;
			}while(iNeedToWrite > 0);
		};
	};
};

void FileSystem_Sync()
{
	if(NULL == Impl)
		return;

	FileMap::iterator It = Impl->myFileMap.begin();
	FileMap::iterator ItEnd = Impl->myFileMap.end();

	for(; It != ItEnd; ++It)
		FileSystem_SyncFile(&(It->second));
};

void FileSystem_Syncer(void*)
{
	do
	{
		Thread::ProtectFromCancel(false);
		/* sleep is thread cancel point, we dont need test cancel here*/
		sleep(30);
		/* but we must to protect sync function. */
		Thread::ProtectFromCancel(true);
		FileSystem_Sync();
		Thread::ProtectFromCancel(false);
	}
	while(NULL != Impl);
};

MBlock & FileSystem_GetBlock(FileHandle * file, SizeType iSector)
{
	assert((NULL != file));

	MBlockMap::iterator It = file->pFile->myBlockMap.find(iSector);
	if(file->pFile->myBlockMap.end() == It)
	{
		/* TODO: not thread safe yet! */
		unsigned char data[MBLOCK_DEFAULT_SIZE];
		const SizeType iPos = iSector * MBLOCK_DEFAULT_SIZE;
		SizeType iReaded = 0;
		do
		{
			const ssize_t iRet = ::pread(file->pFile->iFD, data + iReaded, 
				MBLOCK_DEFAULT_SIZE - iReaded, iPos + iReaded);
			if(-1 == iRet)
			{
				if((EINTR == errno)||(EAGAIN == errno))
					continue;
				throw std::runtime_error(String("Cannot read block from file %d at %"PRIu64": %d\n",
					file->pFile->iFD, iPos + iReaded, errno));
			}
			else if(0 == iRet)
			{ /* nothing to read more. */
				break;
			};

			iReaded += iRet;	
		}while(iReaded < MBLOCK_DEFAULT_SIZE);

		It = file->pFile->myBlockMap.insert(std::make_pair(iSector, MBlock(data, iReaded))).first;
	};
	return It->second;
};
